package com.whz.generic;

import lombok.experimental.UtilityClass;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * 数字与字节转换类,(大端)表示的byte数组辅助类,更多的参考 {@link ByteBuffer}
 *
 * @author whz
 */
@UtilityClass
public class ByteUtils {

    public static final int INT4 = 4;
    public static final int INT2 = 2;
    public static final int INT8 = 8;

    public static byte int3(int x) {
        return toByte(x, 24);
    }

    public static byte int2(int x) {
        return toByte(x, 16);
    }

    public static byte int1(int x) {
        return toByte(x, INT8);
    }

    public static byte int0(int x) {
        return toByte(x, 0);
    }

    public static byte long7(long x) {
        return toByte(x, 56);
    }

    public static byte long6(long x) {
        return toByte(x, 48);
    }

    public static byte long5(long x) {
        return toByte(x, 40);
    }

    public static byte long4(long x) {
        return toByte(x, 32);
    }

    public static byte long3(long x) {
        return toByte(x, 24);
    }

    public static byte long2(long x) {
        return toByte(x, 16);
    }

    public static byte long1(long x) {
        return toByte(x, INT8);
    }

    public static byte long0(long x) {
        return toByte(x, 0);
    }

    private static byte toByte(long x, int loopSize) {
        return (byte) (x >> loopSize & 0xff);
    }

    public static int makeInt(byte b3, byte b2, byte b1, byte b0) {
        return b3 << 24 |
               (b2 & 0xff) << 16 |
               (b1 & 0xff) << INT8 |
               b0 & 0xff;
    }

    public static long makeLong(byte b7, byte b6, byte b5, byte b4,
                                byte b3, byte b2, byte b1, byte b0) {
        return (long) b7 << 56 |
               ((long) b6 & 0xff) << 48 |
               ((long) b5 & 0xff) << 40 |
               ((long) b4 & 0xff) << 32 |
               ((long) b3 & 0xff) << 24 |
               ((long) b2 & 0xff) << 16 |
               ((long) b1 & 0xff) << INT8 |
               (long) b0 & 0xff;
    }

    public static short makeShort(byte b1, byte b0) {
        return (short) (b1 << INT8 | b0 & 0xff);
    }

    public static int getIntQuickly(byte[] bytes) {
        return getIntQuickly(bytes, true);
    }

    /**
     * 数组快速转换成int数字
     *
     * @param bytes       字节数组
     * @param isBigEndian 是否大端表示
     * @return int数
     */
    public static int getIntQuickly(byte[] bytes, boolean isBigEndian) {
        if (bytes == null || bytes.length < INT4) {
            throw new IndexOutOfBoundsException("byte array length must is 4");
        }
        if (isBigEndian) {
            return makeInt(bytes[0], bytes[1], bytes[INT2], bytes[3]);
        } else {
            return makeInt(bytes[3], bytes[INT2], bytes[1], bytes[0]);
        }
    }

    public static byte[] getBytesFromIntQuickly(int num) {
        return getBytesFromIntQuickly(num, true);
    }

    public static byte[] getBytesFromIntQuickly(int num, boolean isBigEndian) {
        if (isBigEndian) {
            return new byte[] {
                    int3(num),
                    int2(num),
                    int1(num),
                    int0(num),
            };
        } else {
            return new byte[] {
                    int0(num),
                    int1(num),
                    int2(num),
                    int3(num),
            };
        }
    }

    public static byte[] getBytesFromShortQuickly(short num) {
        return getBytesFromShortQuickly(num, true);
    }

    public static byte[] getBytesFromShortQuickly(short num, boolean isBigEndian) {
        if (isBigEndian) {
            return new byte[] {
                    int1(num),
                    int0(num),
            };
        } else {
            return new byte[] {
                    int0(num),
                    int1(num),
            };
        }
    }

    /**
     * 快速获取long数字的字节数字,默认大端表示
     *
     * @param num long数字
     * @return 大端表示的字节数字
     */
    public static byte[] getBytesFromLongQuickly(long num) {
        return getBytesFromLongQuickly(num, true);
    }

    public static byte[] getBytesFromLongQuickly(long num, boolean isBigEndian) {
        if (isBigEndian) {
            return new byte[] {
                    long7(num),
                    long6(num),
                    long5(num),
                    long4(num),
                    long3(num),
                    long2(num),
                    long1(num),
                    long0(num),
            };
        } else {
            return new byte[] {
                    long0(num),
                    long1(num),
                    long2(num),
                    long3(num),
                    long4(num),
                    long5(num),
                    long6(num),
                    long7(num),
            };
        }
    }

    public static short getShortQuickly(byte[] bytes) {
        return getShortQuickly(bytes, true);
    }

    /**
     * 数组快速转换成short数字
     *
     * @param bytes       字节数组
     * @param isBigEndian 制定是否是大端
     * @return short数字
     */
    public static short getShortQuickly(byte[] bytes, boolean isBigEndian) {
        if (bytes == null || bytes.length < INT2) {
            throw new IndexOutOfBoundsException("byte array length must is 2");
        }
        if (isBigEndian) {
            return makeShort(bytes[0], bytes[1]);
        } else {
            return makeShort(bytes[1], bytes[0]);
        }
    }

    public static long getLongQuickly(byte[] bytes) {
        return getLongQuickly(bytes, true);
    }

    public static long getLongQuickly(byte[] bytes, boolean isBigEndian) {
        if (bytes == null || bytes.length < INT8) {
            throw new IndexOutOfBoundsException("byte array length must is 8");
        }
        if (isBigEndian) {
            return makeLong(bytes[0], bytes[1], bytes[INT2], bytes[3], bytes[INT4], bytes[5], bytes[6],
                            bytes[7]);
        } else {
            return makeLong(bytes[7], bytes[6], bytes[5], bytes[INT4], bytes[3], bytes[INT2], bytes[1],
                            bytes[0]);
        }
    }

    /**
     * 大端表示的数组转成int数
     *
     * @param bytes 大端表示的数组
     * @return int数
     */
    public static int getInt(byte[] bytes) {
        return getInt(bytes, true);
    }

    /**
     * 字节数字转换成int
     *
     * @param bytes 字节数字
     * @return 字节数字的int数字
     */
    public static int getInt(byte[] bytes, boolean isBigEndian) {
        return ByteBuffer.wrap(bytes)
                         .order(isBigEndian? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN)
                         .getInt();
    }

    /**
     * 按照大端表示转换成short
     *
     * @param bytes 大端表示的字节数组
     * @return short数字
     */
    public static short getShort(byte[] bytes) {
        return getShort(bytes, true);
    }

    public static short getShort(byte[] bytes, boolean isBigEndian) {
        return ByteBuffer.wrap(bytes)
                         .order(isBigEndian? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN)
                         .getShort();
    }

    public static long getLong(byte[] bytes) {
        return getLong(bytes, true);
    }

    public static long getLong(byte[] bytes, boolean isBigEndian) {
        return ByteBuffer.wrap(bytes)
                         .order(isBigEndian? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN)
                         .getLong();
    }

    public static byte[] getBytesFromInt(int num, boolean isBigEndian) {
        byte[] bytes = new byte[INT4];
        ByteBuffer buf = ByteBuffer.wrap(bytes)
                                   .order(isBigEndian? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN)
                                   .putInt(num);
        // 写入后坐标要复位,便于之后的读取
        // 此处不用slice(),较少额外的对象创建
        buf.flip();
        buf.get(bytes);
        return bytes;
    }

    public static byte[] getBytesFromLong(long num, boolean isBigEndian) {
        byte[] bytes = new byte[INT8];
        ByteBuffer buf = ByteBuffer.wrap(bytes)
                                   .order(isBigEndian? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN)
                                   .putLong(num);
        // 写入后坐标要复位,便于之后的读取
        // 此处不用slice(),较少额外的对象创建
        buf.flip();
        buf.get(bytes);
        return bytes;
    }

    public static byte[] getBytesFromShort(short num, boolean isBigEndian) {
        byte[] bytes = new byte[INT2];
        ByteBuffer buf = ByteBuffer.wrap(bytes)
                                   .order(isBigEndian? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN)
                                   .putShort(num);
        // 写入后坐标要复位,便于之后的读取
        // 此处不用slice(),较少额外的对象创建
        buf.flip();
        buf.get(bytes);
        return bytes;
    }
}